package com.wangxiaonan.coupon.config;

import com.wangxiaonan.coupon.common.Operation;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Transaction;

/**
 * @Author: wangxiaonan
 * @Date: 2019/1/10
 **/
public class RedisManeger {

    private static final Logger logger = LoggerFactory.getLogger(RedisManeger.class);
    private static final Long UNLOCK_MSG = 1L;
    /**
     * nxxx NX|XX, XX -- Only set the key if it already exist.
     */
    private static final String SET_IF_EXIST = "XX";
    /**
     * EX|PX, expire time units: EX = seconds.
     */
    private static final String EXPIRE_SEC_TIME = "EX";

    /**
     * Status code reply
     */
    private static final String LOCK_MSG = "OK";
    /**
     * nxxx NX|XX, NX -- Only set the key if it does not already exist.
     */
    private static final String SET_IF_NOT_EXIST = "NX";
    /**
     * EX|PX, expire time units: PX = milliseconds.
     */
    private static final String EXPIRE_MILLSEC_TIME = "PX";
    private static final String KEY_PREFIX = "lock@";

    private static JedisPool getJedisPool() {
        return StaticHolder.jedisPool;
    }

    /**************  redis operations start  ****************/

    /**
     * setString
     */
    public static boolean set(String key, String defaultValue) {
        boolean result;
        try (Jedis jedis = getJedisPool().getResource()) {
            jedis.set(key, defaultValue);
            result = true;
        }
        return result;
    }

    public static String get(String key) {
        String result = null;
        if (key == null || "".equals(key)) {
            return result;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            result = jedis.get(key);
        }
        return result;
    }

    public static long Incrby(String key, long increment) {
        try (Jedis jedis = getJedisPool().getResource()) {
            return jedis.incrBy(key, increment);
        }
    }

    /**
     * 1: the timeout was set. 0: the timeout was not set
     */
    public static long expiry(String key, int second) {
        long result = 0;
        if (null == key || "".equals(key)) {
            return result;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            result = jedis.expire(key, second);
        }
        return result;
    }

    /**
     * 1: the timeout was set. 0: the timeout was not set
     *
     * @param unixTime Date.getTime()
     */
    public static long expiryAt(String key, long unixTime) {
        long result = 0;
        if (null == key || "".equals(key)) {
            return 0;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            result = jedis.expireAt(key, unixTime);
        }
        return result;
    }

    public static void psubscribe(JedisPubSub listener, String... patterns) {
        try (Jedis jedis = getJedisPool().getResource()) {
            jedis.psubscribe(listener, patterns);
        }
    }

    /**
     * key是否存在
     */
    public static long exists(String... key) {
        long result = 0;
        try (Jedis jedis = getJedisPool().getResource()) {
            result = jedis.exists(key);
        }
        return result;
    }

    /**
     * Integer reply, returns the remaining time to live in seconds of a key that has an EXPIRE.
     * In Redis 2.6 or older, if the Key does not exists or does not have an associated expire, -1 is returned.
     * In Redis 2.8 or newer, if the Key does not have an associated expire, -1 is returned
     * or if the Key does not exists, -2 is returned.
     */
    public static long ttl(String key) {
        long result;
        try (Jedis jedis = getJedisPool().getResource()) {
            result = jedis.ttl(key);
        }
        return result;
    }

    /**
     * 分布式锁
     */
    public static String lock(String lockName, long acquireTimeoutInMS, long lockTimeoutInMS) {
        if (StringUtils.isBlank(lockName)) {
            return null;
        }
        String retIdentifier = null;
        String identifier = UUID.randomUUID().toString();
        String lockKey = "lock@" + lockName;
        long end = System.currentTimeMillis() + acquireTimeoutInMS;
        try (Jedis jedis = getJedisPool().getResource()) {
            while (System.currentTimeMillis() < end) {
                String code = jedis.set(lockKey, identifier, SET_IF_NOT_EXIST, EXPIRE_MILLSEC_TIME, lockTimeoutInMS);
                if (LOCK_MSG.equals(code)) {
                    retIdentifier = identifier;
                    break;
                }
                if (jedis.ttl(lockKey) == -1) {
                    jedis.pexpire(lockKey, lockTimeoutInMS);
                }

                try {
                    Thread.sleep(10);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        } catch (Exception ex) {
            logger.error("[RedisManagerSup] lock 获取锁出错 [lockName= {} ] message: ", lockName, ex.getMessage());
        }
        return retIdentifier;
    }

    /**
     * 释放锁
     */
    public static boolean releaseLock(String lockName, String token) {
        boolean isRelease = false;
        if (lockName == null || "".equals(lockName) || token == null || "".equals(token)) {
            return isRelease;
        }
        String lockKey = KEY_PREFIX + lockName;
        try (Jedis jedis = getJedisPool().getResource()) {
            while (true) {
                jedis.watch(lockKey);
                if (token.equals(jedis.get(lockKey))) {
                    Transaction tx = jedis.multi();
                    tx.del(lockKey);
                    List<Object> ret = tx.exec();
                    if (ret == null) {
                        continue;
                    }
                    isRelease = true;
                }
                jedis.unwatch();
                break;
            }
        } catch (Exception e) {
            System.out.println("释放锁失败");
        }

        return isRelease;
    }

    /**
     * Jedis出异常的时候，回收jedis对象资源
     * 此方法已被丢弃，改用close替换，
     */
    @Deprecated
    private static void returnBrokenResource(Jedis jedis) {
        if (null != jedis) {
            getJedisPool().returnBrokenResource(jedis);
        }
    }

    public static long lLen(String key) {
        long length = 0L;
        if (StringUtils.isBlank(key)) {
            return length;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            return jedis.llen(key);
        }
    }

    /**
     * 命令将一个或多个值插入到列表头部。 如果 key 不存在，一个空列表会被创建并执行 LPUSH 操作
     *
     * @return 返回当前列表的长度
     */
    public static long lPush(String key, String... value) {
        long length = 0L;
        if (StringUtils.isBlank(key)) {
            return length;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            length = jedis.lpush(key, value);
        }
        return length;
    }

    /**
     * 命令用于移除并返回列表的最后一个元素
     */
    public static String rPop(String key) {
        if (StringUtils.isBlank(key)) {
            return null;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            return jedis.rpop(key);
        }
    }

    /**
     * 命令用于移除列表的最后一个元素，并将该元素添加到另一个列表并返回
     */
    public static String rPopLPush(String srcKey, String dstKey) {
        if (StringUtils.isBlank(srcKey) || StringUtils.isBlank(dstKey)) {
            return null;
        }
        try (Jedis jedis = getJedisPool().getResource()) {
            return jedis.rpoplpush(srcKey, dstKey);
        }
    }

    public static void pipelineOperate(Operation operation) {
        try (Jedis jedis = getJedisPool().getResource()) {
            Pipeline pipeline = jedis.pipelined();
            operation.execute(pipeline);
            pipeline.sync();
            pipeline.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Jedis 为哈希表 key 中的指定字段的整数值加上增量 increment
     */
    public static long HincrBy(String key, String field, long value) {
        long result;
        try (Jedis jedis = getJedisPool().getResource()) {
            result = jedis.hincrBy(key, field, value);
        }
        return result;
    }
    /**************  redis operations end  ****************/

    /**
     * 创建单例对象
     */
    private static class StaticHolder {

        static final GenericObjectPoolConfig poolConfig;
        static final JedisPool jedisPool;

        static {
            poolConfig = new GenericObjectPoolConfig();
            poolConfig.setMaxIdle(5);
            poolConfig.setMinIdle(2);
            poolConfig.setMaxTotal(10);
            poolConfig.setMaxWaitMillis(5000);
            poolConfig.setTestOnBorrow(true);
            poolConfig.setTestWhileIdle(false);
//            JedisSentinelPool使用的是Sentinel架构形式下的Redis缓存服务
//            Set<String> sentinels = new HashSet<String>();
//            sentinels.add("127.0.0.1:6379");
//            jedisPool = new JedisSentinelPool("mymaster", sentinels, poolConfig);

//            SharedJedisPool采用一致性哈希实现分布式存储

//            jedisPool是普通的实现形式
            jedisPool = new JedisPool(poolConfig, "127.0.0.1", 6379);
        }

    }
}
